package recommend;

import Jama.Matrix;

public class DataObject {

	private double LAMDA;
	private int DIMENSION;
	private int EPOCH;
	private int USER;
	private int ITEM;

	private double[][] rating;
	private double avgRating;
	private double[] userAvgBias;
	private double[] itemAvgBias;
	private double[][] baseline;

	private static double[][] userFeature;
	private static double[][] itemFeature;
	private final double INITIALFEATURE = 0.1;

	private double[][] regSqErrReduceRatio;
	private String[] curveLabel;
	private double[][] regSqErrorSeries;

	public DataObject(double LAMDA, int DIMENSION, int EPOCH, int USER,
			int ITEM, double[][] rating) {
		this.LAMDA = LAMDA;
		this.DIMENSION = DIMENSION;
		this.EPOCH = EPOCH;
		this.USER = USER;
		this.ITEM = ITEM;
		this.initRating();
		this.rating = rating;
		// calculate average and biases
		this.initAvgRating();
		this.initBias();
		this.initBaseline();
		// create and initialize the user-feature and item-feature
		// matrices(vectors)
		this.initFeature();
		this.initRegSqErr();
	}

	private void initRating() {
		if (this.USER > 0 && this.ITEM > 0)
			this.rating = new double[this.USER][this.ITEM];
		else
			System.out
					.println("USER and ITEM numbers are not ready for initializing rating matrix.");
	}

	public double getRating(int u, int i) {
		return rating[u][i];
	}

	public void setRating(double[][] rating) {
		this.rating = rating;
	}

	private void initAvgRating() {
		double result = 0;
		double num = 0;
		for (int u = 0; u < this.USER; u++) {
			for (int i = 0; i < this.ITEM; i++) {
				if (this.rating[u][i] > 0) {
					num++;
					result += this.rating[u][i];
				}
			}
		}
		if (num == 0)
			this.avgRating = 0;
		else
			this.avgRating = result / num;
	}

	public double getAvgRating() {
		return avgRating;
	}

	public void setAvgRating(double avgRating) {
		this.avgRating = avgRating;
	}

	private void initBias() {
		if (this.USER > 0 && this.ITEM > 0) {
			this.userAvgBias = new double[this.USER];
			this.itemAvgBias = new double[this.ITEM];
			System.out.println("");
			// for(int u = 0; u < this.USER; u ++) {
			// System.out.printf("   u%3f", u);
			// }
			for (int u = 0; u < this.USER; u++) {
				this.userAvgBias[u] = this.userAvgBias(u);
				System.out.printf("   %3.2f", userAvgBias[u]);
			}
			System.out.println("");
			// for(int i = 0; i < this.ITEM; i ++) {
			// System.out.printf("   i%3f", i);
			// }
			for (int i = 0; i < this.ITEM; i++) {
				this.itemAvgBias[i] = this.itemAvgBias(i);
				System.out.printf("   %3.2f", itemAvgBias[i]);
			}
		} else
			System.out
					.println("USER and ITEM numbers are not ready for initializing bias vectors.");
	}

	private double userAvgBias(int u) {
		double result = 0;
		double num = 0;
		for (int i = 0; i < this.ITEM; i++) {
			if (this.rating[u][i] > 0) {
				num++;
				result += this.rating[u][i];
			}
		}
		if (num == 0)
			return 0;
		else
			return result / num - this.avgRating;
	}

	private double itemAvgBias(int i) {
		double result = 0;
		double num = 0;
		for (int u = 0; u < this.USER; u++) {
			if (this.rating[u][i] > 0) {
				num++;
				result += this.rating[u][i];
			}
		}
		if (num == 0)
			return 0;
		else
			return result / num - this.avgRating;
	}

	public double getUserAvgBias(int u) {
		return userAvgBias[u];
	}

	public void setUserAvgBias(double[] userAvgBias) {
		this.userAvgBias = userAvgBias;
	}

	public double getItemAvgBias(int i) {
		return itemAvgBias[i];
	}

	public void setItemAvgBias(double[] itemAvgBias) {
		this.itemAvgBias = itemAvgBias;
	}

	private void initBaseline() {
		this.baseline = new double[this.USER][this.ITEM];
		for (int u = 0; u < this.USER; u++) {
			for (int i = 0; i < this.ITEM; i++) {
				this.predictBaseline(u, i);
			}
		}
	}

	public double getBaseline(int u, int i) {
		return baseline[u][i];
	}

	public void setBaseline(double[][] baseline) {
		this.baseline = baseline;
	}

	private double predictBaseline(int u, int i) {
		return this.avgRating + this.itemAvgBias[i] + this.userAvgBias[u];
	}

	private void initFeature() {
		if (this.USER > 0 && this.ITEM > 0 && this.DIMENSION > 0) {
			userFeature = new double[this.USER][this.DIMENSION];
			itemFeature = new double[this.ITEM][this.DIMENSION];
			for (int u = 0; u < this.USER; u++) {
				for (int f = 0; f < this.DIMENSION; f++)
					userFeature[u][f] = this.INITIALFEATURE;
			}
			for (int i = 0; i < this.ITEM; i++) {
				for (int f = 0; f < this.DIMENSION; f++)
					itemFeature[i][f] = this.INITIALFEATURE;
			}
		} else
			System.out
					.println("USER, ITEM and DIMENSION numbers are not ready for initializing feature matrix.");
	}

	public double[][] getUserFeature() {
		return userFeature;
	}

	public double getUserFeature(int u, int f) {
		return userFeature[u][f];
	}

	public void setUserFeature(double[][] newUserFeature) {
		userFeature = newUserFeature;
	}

	public void updateUserFeature(int USER, int DIMENSION, double value) {
		userFeature[USER][DIMENSION] = value;
	}

	public double[][] getItemFeature() {
		return itemFeature;
	}

	public double getItemFeature(int i, int f) {
		return itemFeature[i][f];
	}

	public void setItemFeature(double[][] newItemFeature) {
		itemFeature = newItemFeature;
	}

	public void updateItemFeature(int ITEM, int DIMENSION, double value) {
		itemFeature[ITEM][DIMENSION] = value;
	}

	private void initRegSqErr() {
		this.regSqErrReduceRatio = new double[this.DIMENSION][this.EPOCH];
		this.curveLabel = new String[this.DIMENSION];
		this.regSqErrorSeries = new double[this.DIMENSION][this.EPOCH + 1];
	}

	public double[][] getRegSqErrReduceRatio() {
		return regSqErrReduceRatio;
	}

	public void setRegSqErrReduceRatio(double[][] regSqErrReduceRatio) {
		this.regSqErrReduceRatio = regSqErrReduceRatio;
	}

	public String[] getCurveLabel() {
		return curveLabel;
	}

	public void setCurveLabel(String[] curveLabel) {
		this.curveLabel = curveLabel;
	}

	public void updateCurveLabel(int f, String value) {
		this.curveLabel[f] = value;
	}

	public double[][] getRegSqErrorSeries() {
		return regSqErrorSeries;
	}

	public double getRegSqErrorSeries(int f, int r) {
		return regSqErrorSeries[f][r];
	}

	public void setRegSqErrorSeries(double[][] regSqErrorSeries) {
		this.regSqErrorSeries = regSqErrorSeries;
	}

	public void updateRegSqErrorSeries(int f, int round, double value) {
		this.regSqErrorSeries[f][round] = value;
	}

	public double predictRating(double baseline, double[] pu, double[] qi) {
		return baseline + this.dotProduct(pu, qi);
	}

	public double dotProduct(double[] pu, double[] qi) {
		double result = 0;
		for (int k = 0; k < this.DIMENSION; k++) {
			result += pu[k] * qi[k];
		}
		return result;
	}

	public double regSqError() {
		double result = 0;
		for (int u = 0; u < this.USER; u++) {
			for (int i = 0; i < this.ITEM; i++) {
				if (this.rating[u][i] > 0) {
					double bi = this.itemAvgBias[i];
					double bu = this.userAvgBias[u];
					double baseline = this.getBaseline(u, i);

					double[] pu = userFeature[u];
					double[] qi = itemFeature[i];
					double eui = this.rating[u][i]
							- this.predictRating(baseline, pu, qi);
					Matrix puMatrix = new Matrix(pu, this.DIMENSION);
					Matrix qiMatrix = new Matrix(qi, this.DIMENSION);
					result += Math.pow(eui, 2)
							+ LAMDA
							* (Math.pow(puMatrix.norm1(), 2)
									+ Math.pow(qiMatrix.norm1(), 2)
									+ Math.pow(bu, 2) + Math.pow(bi, 2));
					// result += Math.pow(eui, 2);
				}
			}
		}
		return result;
	}
}
